#!/usr/bin/env python

#Copyright 2008 Adam A. Crossland
#
#Licensed under the Apache License, Version 2.0 (the "License");
#you may not use this file except in compliance with the License.
#You may obtain a copy of the License at
#
#http://www.apache.org/licenses/LICENSE-2.0
#
#Unless required by applicable law or agreed to in writing, software
#distributed under the License is distributed on an "AS IS" BASIS,
#WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#See the License for the specific language governing permissions and
#limitations under the License.

import sys
import os.path

APPENGINE_PATH = '../../../../google/google_appengine'

# Add app-engine related libraries to your path
paths = [
    APPENGINE_PATH,
    os.path.join(APPENGINE_PATH, 'lib', 'django'),
    os.path.join(APPENGINE_PATH, 'lib', 'webob'),
    os.path.join(APPENGINE_PATH, 'lib', 'yaml', 'lib')
]
for path in paths:
  if not os.path.exists(path):
    raise 'Path does not exist: %s' % path
sys.path = paths + sys.path

import unittest
from google.appengine.api import apiproxy_stub_map
from google.appengine.api import datastore_file_stub
from google.appengine.api import mail_stub
from google.appengine.api import user_service_stub
from google.appengine.ext import webapp
from google.appengine.ext import db
from google.appengine.api.memcache import memcache_stub
from sluggable import *

APP_ID = u'sluggable'
AUTH_DOMAIN = 'gmail.com'
LOGGED_IN_USER = 'me@example.com'  # set to '' for no logged in user

BLOG_NAME='test_blog'

os.environ['AUTH_DOMAIN'] = AUTH_DOMAIN
os.environ['USER_EMAIL'] = LOGGED_IN_USER
os.environ['APPLICATION_ID'] = APP_ID

class BlogIndex(db.Model):
    "A global counter used to provide the index of the next blog post."
    index = db.IntegerProperty(required=True, default=0)
    "The next available index for a Post."

class MyTest(unittest.TestCase):

    def setUp(self):

      # Start with a fresh api proxy.
      apiproxy_stub_map.apiproxy = apiproxy_stub_map.APIProxyStubMap()

      # Use a fresh stub datastore.
      stub = datastore_file_stub.DatastoreFileStub(APP_ID, '/dev/null', '/dev/null')
      apiproxy_stub_map.apiproxy.RegisterStub('datastore_v3', stub)

      # Use a fresh stub UserService.
      apiproxy_stub_map.apiproxy.RegisterStub(
          'user', user_service_stub.UserServiceStub())

    def testSimpleSlugAdding(self):
        new_post = Post.new_post(new_title='test post 1', new_body='This is a test post.  Please ignore.')
        assert new_post is not None
        assert new_post.slug is not None
        assert type(new_post.slug) is Slug

        self.assertEqual(new_post.slug.slug_text, "test-post-1")
        self.assertEqual(new_post.slugged_as, "test-post-1")

    def testDatastoreCompatibility(self):
        # These tests seek to ensure that we are accounting for any data
        # that might not be compatible restrictions of the AppEngine Datastore.

        # Datastore keys can not start with numbers.
        new_post = Post.new_post(new_title='123 is a valid title', new_body='This is a test post.  Please ignore.')
        assert new_post is not None
        assert new_post.slug is not None
        assert type(new_post.slug) is Slug
        self.assertEqual(new_post.slugged_as, "123-is-a-valid-title")

    def testDuplicateSlugAdding(self):
        # When client code attempts to create duplicate slugs, the Sluggable
        # code should automatically attempt to add an index value to the end
        # of the slug to differentiate it.
        new_post = Post.new_post(new_title='Dupe Testing post 1', new_body='This is a test post.  Please ignore.')
        assert new_post is not None
        assert new_post.slug is not None
        assert type(new_post.slug) is Slug

        new_post2 = Post.new_post(new_title='Dupe Testing post 1', new_body='This is another test post.  Please ignore.')
        assert new_post2 is not None
        assert new_post2.slug is not None
        assert type(new_post2.slug) is Slug

        new_post3 = Post.new_post(new_title='Dupe Testing post 1', new_body='This is a third test post.  Please ignore.')
        assert new_post3 is not None
        assert new_post3.slug is not None
        assert type(new_post3.slug) is Slug

        self.assertEqual(new_post.slugged_as, "dupe-testing-post-1")
        self.assertEqual(new_post2.slugged_as, "dupe-testing-post-1_1")
        self.assertEqual(new_post3.slugged_as, "dupe-testing-post-1_2")

    def testSimpleRetreivalBySlug(self):
        # Make sure that we can get entities out of the datastore by referring
        # to them by their slugs.
        new_post = Post.new_post(new_title='Test Post 1', new_body='This is a test post.  Please ignore.')
        assert new_post is not None
        assert new_post.slug is not None
        assert type(new_post.slug) is Slug
        self.assertEqual(new_post.slugged_as, "test-post-1")

        found_post = Post.find_by_slug("test-post-1")
        assert found_post is not None
        self.assertEquals(found_post.slugged_as, "test-post-1")
        self.assertEquals(found_post.body, "This is a test post.  Please ignore.")
        self.assertEquals(found_post.title, "Test Post 1")

    def testBadRetreivalBySlug(self):
        # Test a known-negative case, where lookup will fail
        found_post = Post.find_by_slug("foo-bar-bletch")
        assert found_post is None

    def testSlugDeletion(self):
        new_post = Post.new_post(new_title='Test Post 10', new_body='This is a test post.  Please ignore.')
        assert new_post is not None
        assert new_post.slug is not None
        assert type(new_post.slug) is Slug
        self.assertEqual(new_post.slugged_as, "test-post-10")

        found_post = Post.find_by_slug("test-post-10")
        assert found_post is not None
        self.assertEquals(found_post.slugged_as, "test-post-10")
        self.assertEquals(found_post.body, "This is a test post.  Please ignore.")
        self.assertEquals(found_post.title, "Test Post 10")

        new_post.delete_slug()

        found_post2 = Post.find_by_slug("test-post-10")
        assert found_post2 is None

    def testSlugCollision(self):
        new_post = Post.new_post(new_title='Collision Test Post 1', new_body='Testing slug collision handling.')
        assert new_post is not None
        self.assertEqual(new_post.slugged_as, "collision-test-post-1")

        new_post1 = Post.new_post(new_title='Collision Test Post 1', new_body='Testing slug collision handling.')
        assert new_post1 is not None
        self.assertEqual(new_post1.slugged_as, "collision-test-post-1_1")

        new_post2 = Post.new_post(new_title='Collision Test Post 1', new_body='Testing slug collision handling.')
        assert new_post2 is not None
        self.assertEqual(new_post2.slugged_as, "collision-test-post-1_2")

        new_post3 = Post.new_post(new_title='Collision Test Post 1', new_body='Testing slug collision handling.')
        assert new_post3 is not None
        self.assertEqual(new_post3.slugged_as, "collision-test-post-1_3")

        new_post4 = Post.new_post(new_title='Collision Test Post 1', new_body='Testing slug collision handling.')
        assert new_post4 is not None
        self.assertEqual(new_post4.slugged_as, "collision-test-post-1_4")

        new_post5 = Post.new_post(new_title='Collision Test Post 1', new_body='Testing slug collision handling.')
        assert new_post5 is not None
        self.assertEqual(new_post5.slugged_as, "collision-test-post-1_5")

        new_post6 = Post.new_post(new_title='Collision Test Post 1', new_body='Testing slug collision handling.')
        assert new_post6 is not None
        self.assertEqual(new_post6.slugged_as, "collision-test-post-1_6")

        new_post7 = Post.new_post(new_title='Collision Test Post 1', new_body='Testing slug collision handling.')
        assert new_post7 is not None
        self.assertEqual(new_post7.slugged_as, "collision-test-post-1_7")

        new_post8 = Post.new_post(new_title='Collision Test Post 1', new_body='Testing slug collision handling.')
        assert new_post8 is not None
        self.assertEqual(new_post8.slugged_as, "collision-test-post-1_8")

        new_post9 = Post.new_post(new_title='Collision Test Post 1', new_body='Testing slug collision handling.')
        assert new_post9 is not None
        self.assertEqual(new_post9.slugged_as, "collision-test-post-1_9")

        new_post10 = Post.new_post(new_title='Collision Test Post 1', new_body='Testing slug collision handling.')
        assert new_post10 is not None
        self.assertEqual(new_post10.slugged_as, "collision-test-post-1_10")

        self.assertRaises(SlugExistsException, Post.new_post, new_title='Collision Test Post 1', new_body='Testing slug collision handling.')

class Post(Sluggable, db.Model):
    index = db.IntegerProperty(required=True, default=0)
    body = db.TextProperty(required = True)
    title = db.StringProperty()
    added = db.DateTimeProperty(auto_now_add=True)
    added_month = db.IntegerProperty()
    added_year = db.IntegerProperty()
    edited = db.DateTimeProperty()

    Sluggable.slug_field_is(title)

    def __init__(self, parent=None, key_name=None, app=None, **entity_values):
        db.Model.__init__(self, parent, key_name, app, **entity_values)

    def get_all_posts():
        return db.GqlQuery("SELECT * from Post ORDER BY added DESC")
    Get_All_Posts = staticmethod(get_all_posts)

    @classmethod
    def get_posts(cls, start_index=0, count=10):
        start_index = int(start_index) # Just make sure that we have an int
        posts = Post.gql('WHERE index <= :1 ORDER BY index DESC', start_index).fetch(count + 1)
        if len(posts) > count:
            posts = posts[:count]

        return posts

    @classmethod
    def new_post(cls, new_title=None, new_body=None, new_tags=[]):
        new_post = None
        if new_title is not None and new_body is not None:
            def txn():
                blog_index = BlogIndex.get_by_key_name(BLOG_NAME)
                if blog_index is None:
                    blog_index = BlogIndex(key_name=BLOG_NAME)
                new_index = blog_index.index
                blog_index.index += 1
                blog_index.put()

                new_post_key_name = BLOG_NAME + str(new_index)
                new_post = Post(key_name=new_post_key_name, parent=blog_index,
                               index = new_index, title = new_title,
                               body = new_body)
                new_post.put()

                return new_post
            new_post = db.run_in_transaction(txn)
            new_post.set_slug()
        else:
            raise Exception("Must supply both new_title and new_body when creating a new Post.")

        return new_post

if __name__ == '__main__':
    unittest.main()
